package lock

import (
	"fmt"
	"runtime/debug"
	"strings"
	"sync"
	"sync/atomic"
	"time"
	"xxgame.com/CommApp/Util/cache"
	"xxgame.com/CommApp/Util/goroutine"
	"xxgame.com/CommApp/Util/log"
	"xxgame.com/CommApp/Util/timer"
)

var LOCK_CACHE_TIME = 30 * time.Second	// 锁对象缓存时间
var UNLOCK_FORCE_TIME = 10 * time.Second // 超时解锁（防死锁，todo：执行该定时器提前解锁会有让数据不安全的风险）

// 封装并发锁类（仅互斥锁的封装，实际上线程不安全，需要上层加锁访问，例如：LockMgr）
type Lock struct {
	lockTag string
	lock    sync.Mutex
	goid    int64 // 占用锁的协程ID
	lockCnt int   // 同一协程被锁次数
	boInit  int32 // 是否初始化（0：未初始化；1：已初始化）
}

// 初始化锁
func (this *Lock) Init(tag string) *Lock {
	this.lockTag = tag
	//this.goid = 0
	this.lockCnt = 0
	atomic.StoreInt64(&this.goid, 0)
	atomic.CompareAndSwapInt32(&this.boInit, 0, 1)
	return this
}

func (this *Lock) UnInit() *Lock {
	this.lockTag = ""
	//this.goid = 0
	this.lockCnt = 0
	atomic.StoreInt64(&this.goid, 0)
	atomic.CompareAndSwapInt32(&this.boInit, 1, 0)
	return this
}

// 上锁
func (this *Lock) Lock() int {
	curGoid := goroutine.GetID()
	lockGoid := atomic.LoadInt64(&this.goid)
	if curGoid == lockGoid && 0 < lockGoid { // 同一个协程访问锁，上锁失败，返回1
		this.lockCnt++
		log.PST.Infof("并发锁 Lock.Lock()，同协程访问同对象，无需上锁："+
			"curGoid=%+v, goid=%+v, lockCnt=%+v, lockTag=%+v", curGoid, lockGoid,
			this.lockCnt, this.lockTag)
		return 1
	}
	this.lock.Lock()
	//this.goid = curGoid // 当前协程占用当前锁
	atomic.StoreInt64(&this.goid, curGoid)
	this.lockCnt++
	// 10秒后自动解锁，防止死锁（如果当前业务执行超过10秒，定时器执行解锁，
	// 实际上，业务逻辑继续执行 UnLock() 的话，会将其他协程获取的锁在意料之外
	// 被解锁，会出现问题，但是如果一个业务超过10秒还没执行完毕，需要排查与优化该业务）
	// todo: 这里考虑不加这个定时器，应为业务访问DB时间过长会导致这里超时，提前解锁，会直接导致数据不安全
	// todo：如果这里非加上这个定时器，就得想办法，让当前业务终止，相当于抛弃当前数据包的处理
	err := timer.Mgr().Start(this.lockTag+"_UnLock",
		UNLOCK_FORCE_TIME, 1, func() {
			this.UnlockByTimer(curGoid)
		})
	if nil != err {
		log.PST.Errorf("并发锁 Lock.Lock(), 不同协程访问同DB对象，加定时器异常："+
			"curGoid=%+v, goid=%+v, lockCnt=%+v, lockTag=%+v, err=%+v", curGoid,
			this.goid, this.lockCnt, this.lockTag, err.Error())
	}
	log.PST.Debugf("并发锁 Lock.Lock()，不同协程访问同DB对象，上锁成功："+
		"curGoid=%+v, goid=%+v, lockCnt=%+v, lockTag=%+v", curGoid, this.goid,
		this.lockCnt, this.lockTag)
	if "" == this.lockTag {
		log.PST.Errorf("并发锁 Lock.Lock()，不同协程访问同DB对象，上锁异常："+
			"curGoid=%+v, goid=%+v, lockCnt=%+v, stack=%+v", curGoid, this.goid,
			this.lockCnt, string(debug.Stack()))
	}
	return 0
}

// 解锁
// 	参数说明：
//		unlockKey为解锁钥匙，使用锁时获得key钥匙
func (this *Lock) UnLock() int {
	curGoid := goroutine.GetID()
	lockGoid := atomic.LoadInt64(&this.goid)
	// 锁已被其他协程占用，直接返回（被定时器解锁后又被其他协程占有锁）
	if curGoid != lockGoid && 0 < lockGoid {
		log.PST.Errorf("并发锁 Lock.UnLock()，锁已被协程%+v占用，"+
			"入侵尝试解锁协程curGoid=%+v, lockTag=%+v", lockGoid, curGoid, this.lockTag)
		return -1
	// 同一协程解锁，扣减锁次数，当所次数等于零时，则去真正解锁
	} else if curGoid == lockGoid && 0 < lockGoid {
		this.lockCnt--
		if 0 < this.lockCnt {
			log.PST.Infof("并发锁 Lock.UnLock()，同协程访问同对象，无需解锁："+
				"curGoid=%+v, goid=%+v, lockCnt=%+v, lockTag=%+v", curGoid, lockGoid,
				this.lockCnt, this.lockTag)
			return 1
		}
	} else {
		log.PST.Errorf("并发锁 Lock.UnLock()，逻辑执行异常，锁可能已超时释放："+
			"curGoid=%+v, goid=%+v, lockTag=%+v", curGoid, lockGoid, this.lockTag)
		return -2
	}
	err := timer.Mgr().Stop(this.lockTag+"_UnLock")
	if nil != err {
		log.PST.Errorf("并发锁 Lock.UnLock()，停止定时器异常："+
			"curGoid=%+v, goid=%+v, lockTag=%+v, err=%+v", curGoid,
			lockGoid, this.lockTag, err.Error())
	}
	// 二次检查锁是否被其他协程占用
	if !atomic.CompareAndSwapInt64(&this.goid, curGoid, 0) {
		lockGoid = atomic.LoadInt64(&this.goid)
		log.PST.Errorf("并发锁 Lock.UnLock()，二次检查，锁已被协程%+v占用，"+
			"入侵尝试解锁协程curGoid=%+v, lockTag=%+v", lockGoid, curGoid, this.lockTag)
		return -1
	}
	//atomic.StoreInt64(&this.goid, 0)
	lockGoid = atomic.LoadInt64(&this.goid)
	this.lockCnt = 0
	// 准备释放锁，说明该对象在当前业务内生命周期需要回收
	// 实际上，将对象从缓存中移除，对象生命周期重新托管给GC处理，否则会出现内存泄漏
	cache.MgrSub().Remove(curGoid, this.lockTag)
	// 这里锁对象需要延迟回收到pool池子，因为可能有n个协程等待该锁对象使用，
	// 否则可能后续的协程在在LockMgr.Unlock中无法找到该锁对象，导致无法成功解锁
	err2 := timer.Mgr().ReStart(this.lockTag+"_LockRemove", LOCK_CACHE_TIME, 1, func() {
		this.Remove(curGoid)
	})
	if nil != err2 {
		log.PST.Errorf("并发锁 Lock.UnLock()，重启定时器异常，移除lockRemove失败："+
			"curGoid=%+v, goid=%+v, lockTag=%+v, err=%+v", curGoid,
			lockGoid, this.lockTag, err2.Error())
	}
	// 最后正真释放对象锁
	this.lock.Unlock()
	log.PST.Debugf("并发锁 Lock.UnLock()，不同协程访问同DB对象，解锁成功："+
		"curGoid=%+v, goid=%+v, lockGoid=%+v, lockCnt=%+v, lockTag=%+v", curGoid,
		this.goid, lockGoid, this.lockCnt, this.lockTag)
	return 0
}

// 被定时器解锁
func (this *Lock) UnlockByTimer(goid int64) {
	// 锁已被其他协程占用，直接返回（被定时器解锁后又被其他协程占有锁）
	lockGoid := atomic.LoadInt64(&this.goid)
	if goid != lockGoid {
		log.PST.Errorf("并发锁 Lock.UnlockByTimer()，锁已被协程%+v占用，"+
			"入侵尝试解锁协程 curGoid=%+v, lockTag=%+v", lockGoid, goid, this.lockTag)
		return
	}
	//this.goid = 0
	atomic.StoreInt64(&this.goid, 0)
	this.lockCnt = 0
	// 准备释放锁，说明该对象在当前业务内生命周期需要回收
	// 实际上，将对象从缓存中移除，对象生命周期重新托管给GC处理，否则会出现内存泄漏
	// todo: 这里定时器协程执行，里面使用普通map，会发生访问竞争冲突
	cache.MgrSub().Remove(goid, this.lockTag)
	// 这里锁对象需要延迟回收到pool池子，因为可能有n个协程等待该锁对象使用，
	// 否则可能后续的协程在在LockMgr.Unlock中无法找到该锁对象，导致无法成功解锁
	err := timer.Mgr().ReStart(this.lockTag+"_LockRemove", LOCK_CACHE_TIME, 1, func() {
		this.Remove(goid)
	})
	if nil != err {
		log.PST.Errorf("并发锁 Lock.UnlockByTimer()，重启定时器异常，" +
			"移除lockRemove失败：curGoid=%+v, goid=%+v, lockCnt=%+v, lockTag=%+v, err=%+v",
			goid, this.goid, this.lockCnt, this.lockTag, err.Error())
	}
	// 最后正真释放对象锁
	this.lock.Unlock()
	log.PST.Debugf("并发锁 Lock.UnlockByTimer()，不同协程访问同DB对象，解锁成功："+
		"curGoid=%+v, goid=%+v, lockCnt=%+v, lockTag=%+v", goid, this.goid,
		this.lockCnt, this.lockTag)
}

// 最后一道锁需要释放锁
func (this *Lock) Remove(curGoid int64) {
	tagList := strings.Split(this.lockTag, "_")
	if "SUB" == tagList[0] { // 移除子项锁
		MgrSub().Remove(curGoid, this.lockTag)
	} else if "CTN" == tagList[0] { // 移除容器锁
		MgrCtn().Remove(curGoid, this.lockTag)
	} else {
		log.PST.Errorf("并发锁 Lock.Remove()，"+
			"非法标签：curGoid=%+v, goid=%+v, lockTag=%+v",
			curGoid, this.goid, this.lockTag)
	}
}

// 获取加锁次数
func (this *Lock) GetLockCnt() int {
	curGoid := goroutine.GetID()
	if curGoid != this.goid {
		log.PST.Errorf("并发锁 Lock.GetLockCnt()，锁已被协程%+v占用，"+
			"入侵获取锁计数协程curGoid=%+v, lockTag=%+v", this.goid, curGoid, this.lockTag)
		return 0
	}
	return this.lockCnt
}

// 获取锁占用协程ID
func (this *Lock) GetGoid() int64 {
	return this.goid
}

// 并发锁管理类
type LockMgr struct {
	lockMap sync.Map   // 仅仅同步map保护数据，无法保护过程
	elemNum int32        // 已出发申请对象次数
	pool    *sync.Pool  // 锁对象池子
	lockNum	int32		// 使用中的锁数量
}

var initOnceSub sync.Once
var initOnceCtn sync.Once
var gLockMgrSub *LockMgr
var gLockMgrCtn *LockMgr

// 子项锁管理器（单例）
func MgrSub() *LockMgr {
	initOnceSub.Do(func() {
		gLockMgrSub = newLockMgr()
	})
	return gLockMgrSub
}

// 容器锁管理器（单例）
func MgrCtn() *LockMgr {
	initOnceCtn.Do(func() {
		gLockMgrCtn = newLockMgr()
	})
	return gLockMgrCtn
}

// 内部接口：生成锁管理器对象指针
func newLockMgr() *LockMgr {
	lockMgr := new(LockMgr)
	lockMgr.pool = &sync.Pool {
		New: func() interface{} {
			atomic.AddInt32(&lockMgr.elemNum, 1)
			return new(Lock)
		},
	}
	return lockMgr
}

// 指定tag上锁
func (this *LockMgr) Lock(tag ...interface{}) int {
	tagCnt := len(tag)
	tagFormatList := []string{}
	for i := 0; i < tagCnt; i++ {
		tagFormatList = append(tagFormatList, "%+v")
	}
	tagFormat := strings.Join(tagFormatList, "_")
	lockTag := fmt.Sprintf(tagFormat, tag...)
	curGoid := goroutine.GetID()
	// lock, ok := this.lockMap.Load(lockTag)
	newLock := this.pool.Get().(*Lock).Init(lockTag)
	lock, ok := this.lockMap.LoadOrStore(lockTag, newLock)
	if !ok {
		atomic.AddInt32(&this.lockNum, 1)
		log.PST.Infof("锁管理对象 LockMgr.Lock()，新创建锁："+
			"curGoid=%+v, lockTag=%+v, lock=%+v, lockNew=%+v, lockNum=%+v",
			curGoid, lockTag, lock.(*Lock), this.elemNum, this.lockNum)
	} else {
		this.pool.Put(newLock.UnInit()) // 锁对象已存在，pool需要回收锁对象
		log.PST.Infof("锁管理对象 LockMgr.Lock()，无需创建："+
			"curGoid=%+v, lockTag=%+v, lock=%+v, lockNew=%+v, lockNum=%+v",
			curGoid, lockTag, lock.(*Lock), this.elemNum, this.lockNum)
	}
	return lock.(*Lock).Lock()
}

// 指定tag解锁
func (this *LockMgr) UnLock(tag ...interface{}) {
	tagCnt := len(tag)
	tagFormatList := []string{}
	for i := 0; i < tagCnt; i++ {
		tagFormatList = append(tagFormatList, "%+v")
	}
	tagFormat := strings.Join(tagFormatList, "_")
	lockTag := fmt.Sprintf(tagFormat, tag...)
	lock, ok := this.lockMap.Load(lockTag)
	if ok {
		lock.(*Lock).UnLock()
	} else {
		curGoid := goroutine.GetID()
		log.PST.Errorf("锁管理对象 LockMgr.UnLock()，"+
			"锁对象不存在：curGoid=%+v, lockTag=%+v, stack=%+v",
			curGoid, lockTag, string(debug.Stack()))
	}
}

// 移除锁
func (this *LockMgr) Remove(curGoid int64, tag ...interface{}) {
	tagCnt := len(tag)
	tagFormatList := []string{}
	for i := 0; i < tagCnt; i++ {
		tagFormatList = append(tagFormatList, "%+v")
	}
	tagFormat := strings.Join(tagFormatList, "_")
	lockTag := fmt.Sprintf(tagFormat, tag...)
	// 二次检查lockTag对应的锁是已初始化并且锁对象未被任何协程占用，否则尝试移除
	// Lock时倘若不投入使用的lock对象，无需走当前流程，直接在Lock流程里面回收到pool
	lock, ok := this.lockMap.Load(lockTag)
	if ok {
		// 投入使用并且goid处于0（锁未被任何协程占用）
		if 1 == atomic.LoadInt32(&(lock.(*Lock).boInit)) &&
			0 == atomic.LoadInt64(&(lock.(*Lock).goid)) {
			lock, ok = this.lockMap.Load(lockTag)
			if ok {
				if 1 == atomic.LoadInt32(&(lock.(*Lock).boInit)) &&
					0 == atomic.LoadInt64(&(lock.(*Lock).goid)) {
					lock2, ok2 := this.lockMap.LoadAndDelete(lockTag)
					if ok2 {
						this.pool.Put(lock2.(*Lock).UnInit())
						atomic.AddInt32(&this.lockNum, -1)
						log.PST.Infof("管理锁对象 LockMgr.Remove(), 移除锁成功：curGoid=%+v, " +
							"lockTag=%+v, lockNew=%+v, lockNum=%+v", curGoid, lockTag, this.elemNum, this.lockNum)
					} else {
						log.PST.Errorf("管理锁对象 LockMgr.Remove(), 移除锁失败，锁不存在：curGoid=%+v, "+
							"lockTag=%+v, lockNew=%+v, lockNum=%+v", curGoid, lockTag, this.elemNum, this.lockNum)
					}
				} else {
					log.PST.Infof("管理锁对象 LockMgr.Remove(), 二次尝试移除锁失败，锁已进入待使用状态："+
						"curGoid=%+v, lockTag=%+v", curGoid, lockTag)
				}
			} else {
				log.PST.Errorf("管理锁对象 LockMgr.Remove(), 二次尝试移除锁失败，锁不存在："+
					"curGoid=%+v, lockTag=%+v", curGoid, lockTag)
			}
		} else {
			log.PST.Infof("管理锁对象 LockMgr.Remove(), 一次尝试移除锁失败，锁已进入待使用状态："+
				"curGoid=%+v, lockTag=%+v", curGoid, lockTag)
		}
	} else {
		log.PST.Errorf("管理锁对象 LockMgr.Remove(), 一次尝试移除锁失败，锁不存在："+
			"curGoid=%+v, lockTag=%+v", curGoid, lockTag)
	}
	//lock, ok := this.lockMap.LoadAndDelete(lockTag)
	//curGoid := goroutine.GetID()
	//if ok {
	//	this.pool.Put(lock)
	//	log.PST.Infof("管理锁对象 LockMgr.Remove(), 移除锁成功："+
	//		"curGoid=%+v, lockTag=%+v", curGoid, lockTag)
	//} else {
	//	log.PST.Errorf("管理锁对象 LockMgr.Remove(), 移除锁失败，锁不存在："+
	//		"curGoid=%+v, lockTag=%+v", curGoid, lockTag)
	//}
}

// 判断是否到到最后一把锁
/*
	调这个函数，主要用于Save的执行，如果某业务异常，导致触发定时器解锁，会直接将
	lockCnt在设置为0， 这个时候，这里返回false，这样会导致Save直接return不持久化，
	实际上，在触发定时器解锁后，有可能有其他协程已获得该锁，并且完成相关数据持久化，
	如果这个时候上个异常业务超时解锁，我们可以认为执行异常，数据异常，不执行持久化
	也是情理之中，所以这里可以认为异常业务不执行持久化直接在Save中return

	⭐ 注意：这里的逻辑判断与持久化绑定，所以评估：如果出bug，会影响持久化，但是
		尽早投入使用，让bug尽快出来，然后解决修复！！！！！！！！！！！！！！！！
*/
func (this *LockMgr) IsLastLock(tag ...interface{}) bool {
	tagCnt := len(tag)
	tagFormatList := []string{}
	for i := 0; i < tagCnt; i++ {
		tagFormatList = append(tagFormatList, "%+v")
	}
	tagFormat := strings.Join(tagFormatList, "_")
	lockTag := fmt.Sprintf(tagFormat, tag...)
	lock, ok := this.lockMap.Load(lockTag)
	curGoid := goroutine.GetID()
	if ok {
		goid := lock.(*Lock).GetGoid()
		lockCnt := lock.(*Lock).GetLockCnt()
		if 1 == lockCnt {
			log.PST.Debugf("锁管理器 LockMgr.IsLastLock()，已经最后锁："+
				"curGoid=%+v, goid=%+v, lockCnt=%+v, lockTag=%+v", curGoid,
				goid, lockCnt, lockTag)
			return true
		} else {
			log.PST.Debugf("锁管理器 LockMgr.IsLastLock()，并非最后锁："+
				"curGoid=%+v, goid=%+v, lockCnt=%+v, lockTag=%+v", curGoid,
				goid, lockCnt, lockTag)
		}
	} else {
		log.PST.Errorf("锁管理器 LockMgr.IsLastLock()，锁对象不存在："+
			"curGoid=%+v, lockTag=%+v", curGoid, lockTag)
	}
	return false
}
